/*
 * Copyright (c) 2017 Sprint
 *
 * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The OpenAirInterface Software Alliance licenses this file to You under
 * the terms found in the LICENSE file in the root of this source tree.
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef __FD_H
#define __FD_H

#include <stddef.h>
#include <string.h>
#include <arpa/inet.h>
#include <time.h>

#include <stdexcept>
#include <string>
#include <list>
#include <map>

#include "freeDiameter/freeDiameter-host.h"
#include "freeDiameter/libfdcore.h"
#include "freeDiameter/libfdproto.h"

#include "stime.h"
#include "stimer.h"
#include "sutility.h"

class FDException : public std::runtime_error {
 public:
  FDException(const char* m) : std::runtime_error(m) {}
  FDException(const std::string& m) : std::runtime_error(m) {}
};

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

template<class T>
class FDBuffer {
 public:
  FDBuffer(size_t size) {
    msize = size;
    mbuf  = new T[msize];
  }
  ~FDBuffer() {
    if (mbuf) delete[] mbuf;
  }
  T* get() { return mbuf; }

 private:
  FDBuffer();
  size_t msize;
  T* mbuf;
};

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//
// STARTUP, SHUTDOWN AND ADVERTISING SUPPORT FOR AN APPLICATION
//
//    Initialization of the freeDiameter library is achieved
//    by performing the following operations.
//
//    1. Create an instance of FDEngine that will exist for the
//       duration of the applciation.
//    2. Tell the freeDiameter library what configuration file to
//       use by calling FDEngine::setConfigFile().
//    3. Initialize the freeDiameter library by calling the
//       FDEngine::init() method.  This will only initialize the
//       library, it will not actually start communicating with other
//       diameter endpoints until the start() method is called.
//    4. Call FDEngine::advertiseSupport() for each supported diameter
//       application.  To populate the top level Auth-Application-Id or
//       Acct-Application-Id AVP in the Capabilities-Exchagne request or
//       answer, call the FDEngine::advertiseSupport() method that does
//       NOT accept a FDDictionaryEntryVendor object.  To populate the
//       the top level Vendor-Specific-Application-Id, use the
//       FDEngine::advertiseSupport() method that does accept a
//       FDDictionaryEntryVendor object.
//    5. After advertising support for all of the desired applications,
//       start freeDiameter by calling FDEngine::start().
//    6. At this point, freeDiameter is up and running.  The application
//       can wait for the freeDiameter engine to exit by calling
//       FDEngine::waitForShutdown().  This method will block until
//       freeDiameter has completed it's shutdown process.  The shutdown
//       process can be triggered by calling FDEngine::uninit() from any
//       thread or from a signal handler.
//
// SENDING A REQUEST AND RECEIVING AN ANSWER
//
//   *** SENDING A REQUEST ***
//         Derive new class from FDMessageRequest overriding the
//           processAnswer() virtual method. the processAnswer()
//           method will be called when the answer is received.
//
//           If using the interface specific objects generated by
//           fdtool, it may be necessary to derive a new object
//           from the generated message specific request object,
//           ie s6t::COIRreq, in order to add additional data that
//           may be required when processing the answer.
//
//      1. "new" the derived FDMessageRequest object
//      2. Populate the appropriate AVP's
//      3. Send the request using the send() method
//         *** DO NOT DELETE THE REQUEST OBJECT ***
//
//   *** RECEIVING AN ANSWER ***
//      1. The processAnswer() virtual method will be called
//           with the context of the original derived FDMessageRequest
//           object.
//      2. Inside the processAnswer() virtual method, Create the
//           appropriate FDMessageAnswer (or derived) object and process
//           the answer accordingly.
//      3. When the FDMessageAnswer object goes out of scope (or is
//           deleted), the underlying freeDiameter message will be
//           freed.
//      4. After returning from the processAnswer() method, the
//           FDMessageRequest internals will delete the associated
//           FDMessageRequest object.
//
// RECEIVING A REQUEST AND SENDING AN ANSWER
//
//      Unlike sending a request and receiving an answer where the
//      answer is processed by virtual method associated with the
//      request object, processAnswer(), to process an incoming
//      request, a command hander needs to be registered using the
//      FDApplication::registerHandler method.  If using the fdtool
//      generated interface specific objects, this can be done by
//      uncommenting the appropriate handler in the
//      Application::registerHandlers() method.  Then when an incoming
//      request is received the process() method associated with the
//      command object will be called to process the incoming request.
//
//      *** RECEIVING A REQUEST ***
//
//      1. The process() method of the previously registered command object
//         will be called by the framework.
//      2. Perform whatever processing is required to process the request.
//         If it is necessary to send a request to another application before
//         answering this request, make sure to save the FDMessageRequest*,
//         it will be needed to generate the answer.
//
//      *** SENDING AN ANSWER ***
//
//      1. Construct a FDMessageAnswer object by using the FDMessageRequest*
//         received in the command process() method.  The FDMessageAnswer can
//         be allocated on the stack, it does not need to be created on the heap
//         using "new".
//      2. After adding the necessary AVP's to the answer, send the answer
//      message
//         by calling the FDMessageAnswer::send() method.
//      3. "delete" the FDMessageAnswer object if it was created using "new".
//
// ADDING AVP'S TO A MESSAGE
//
//    AVP's can be added to a message by calling any of the overloaded
//    FDMessage::add() methods.  To add an AVP using a basic data type, the
//    FDDictionaryEntryAVP that describes the AVP must also be passed in to
//    the FDMessage::add() method.  Additionally, a previously FDAvp object
//    can be added using the FDMessage::add() method.  If it is necessary to
//    copy an AVP from one message to another, the FDMessage::add() method
//    will accept any of the FDExtractor objects (FDExtractor, FDExtractorList,
//    FDExtractorAvp or FDExtractorAvpList).
//
// ADDING AVP'S TO A GROUPED AVP
//
//    First, create an FDAvp object using the appropriate FDDictionaryEntryAVP
//    object that describes the grouped AVP.  Then any AVP can be added to the
//    grouped AVP using the same overloads to the FDAvp::add() method that are
//    available in the FDMessage::add() methods.  Once all AVP's have been added
//    to the grouped AVP, add the grouped AVP to it's parent, another grouped
//    AVP or a message, using the add() method on the parent object.
//
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

class FDDictionaryEntryApplication;
class FDDictionaryEntryVendor;

class FDEngine {
 public:
  FDEngine(const char* cfgfile = NULL);
  FDEngine(const std::string& cfgfile);
  ~FDEngine();

  bool init();
  bool start();
  void uninit(bool wait = true);

  void waitForShutdown();

  const std::string& getConfigFile() { return m_cfgfile; }
  const std::string& setConfigFile(const char* cf) { return m_cfgfile = cf; }
  const std::string& setConfigFile(const std::string& cf) {
    return setConfigFile(cf.c_str());
  }

  void advertiseSupport(
      FDDictionaryEntryApplication& app, int auth = 0, int acct = 0);
  void advertiseSupport(
      FDDictionaryEntryApplication& app, FDDictionaryEntryVendor& vendor,
      int auth = 0, int acct = 0);

 private:
  std::string m_cfgfile;
};

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

class FDDictionaryEntry {
 public:
  struct dictionary* getDictionary() const {
    return m_dict;
  }
  struct dict_object* getEntry() const {
    return m_de;
  }
  bool isValid() { return m_de != NULL; }

 protected:
  FDDictionaryEntry();
  FDDictionaryEntry(
      const void* what, enum dict_object_type type, int criteria,
      struct dictionary* dict = NULL);

  void init(
      const void* what, enum dict_object_type type, int criteria,
      struct dictionary* dict = NULL);
  void init(struct dict_object* de, struct dictionary* dict = NULL);

  virtual ~FDDictionaryEntry();

 private:
  struct dictionary* m_dict;
  struct dict_object* m_de;
  bool m_destroy;
};

////////////////////////////////////////////////////////////////////////////////

class FDDictionaryEntryApplication : public FDDictionaryEntry {
 public:
  FDDictionaryEntryApplication(
      const char* name, struct dictionary* dict = NULL);

  const char* getName() const { return m_data.application_name; }
  application_id_t getId() const { return m_data.application_id; }

 private:
  bool init();

  dict_application_data m_data;
};

////////////////////////////////////////////////////////////////////////////////

class FDDictionaryEntryVendor : public FDDictionaryEntry {
 public:
  FDDictionaryEntryVendor(const char* name, struct dictionary* dict = NULL);
  FDDictionaryEntryVendor(vendor_id_t vendorid, struct dictionary* dict = NULL);
  FDDictionaryEntryVendor(
      const FDDictionaryEntryApplication& app, struct dictionary* dict = NULL);

  const char* getName() { return m_data.vendor_name; }
  vendor_id_t getId() { return m_data.vendor_id; }

 private:
  bool init();

  dict_vendor_data m_data;
};

////////////////////////////////////////////////////////////////////////////////

enum DiameterDataType {
  DDTUnknown,
  DDTOctetString,
  DDTI32,
  DDTI64,
  DDTU32,
  DDTU64,
  DDTF32,
  DDTF64,
  DDTGrouped,
  DDTAddress,
  DDTTime,
  DDTUTF8String,
  DDTDiameterIdentity,
  DDTDiameterURI,
  DDTEnumerated,
  DDTIPFilterRule
};

class FDDictionaryEntryAVP : public FDDictionaryEntry {
 public:
  FDDictionaryEntryAVP(
      const char* name, bool allVendors = false,
      struct dictionary* dict = NULL);
  FDDictionaryEntryAVP(
      const char* name, vendor_id_t vendorid, struct dictionary* dict = NULL);
  FDDictionaryEntryAVP(struct dict_object* de);

  bool isDerived() const { return m_isderived; }

  const char* getName() const { return m_basedata.avp_name; }
  vendor_id_t getVendorId() const { return m_basedata.avp_vendor; }
  avp_code_t getAvpCode() const { return m_basedata.avp_code; }
  DiameterDataType getDataType() const { return m_datatype; }

 private:
  void getTypeInfo();

  std::string m_name;
  vendor_id_t m_vendorid;

  struct dict_avp_data m_basedata;
  struct dict_object* m_derivedtype;
  struct dict_type_data m_derivedtypedata;

  bool m_isderived;
  DiameterDataType m_datatype;
};

////////////////////////////////////////////////////////////////////////////////

class FDDictionaryEntryCommand : public FDDictionaryEntry {
 public:
  FDDictionaryEntryCommand(const char* name, struct dictionary* dict = NULL);
  FDDictionaryEntryCommand(
      command_code_t cmdid, struct dictionary* dict = NULL);
  FDDictionaryEntryCommand(
      const FDDictionaryEntryCommand& req, struct dictionary* dict = NULL);

  bool isRequest() const {
    return (m_data.cmd_flag_val & CMD_FLAG_REQUEST) ? true : false;
  }
  bool isAnswer() const {
    return (m_data.cmd_flag_val & CMD_FLAG_REQUEST) ? false : true;
  }

  command_code_t getCommandCode() { return m_data.cmd_code; }

 private:
  struct dict_cmd_data m_data;
};

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

class FDMessage;
class FDExtractor;
class FDExtractorList;
class FDExtractorAvp;
class FDExtractorAvpList;

class FDAvp {
  friend FDMessage;

 public:
  FDAvp(FDDictionaryEntryAVP& de, bool dedel = false);
  FDAvp(FDDictionaryEntryAVP& de, struct avp* a, bool dedel = false);

  ~FDAvp();

  FDAvp& add(FDAvp& avp);

  bool isValid() { return m_avp != NULL; }

  FDAvp& add(FDDictionaryEntryAVP& de, int32_t v) {
    FDAvp avp(de);
    avp = v;
    return add(avp);
  }
  FDAvp& add(FDDictionaryEntryAVP& de, int64_t v) {
    FDAvp avp(de);
    avp = v;
    return add(avp);
  }
  FDAvp& add(FDDictionaryEntryAVP& de, uint32_t v) {
    FDAvp avp(de);
    avp = v;
    return add(avp);
  }
  FDAvp& add(FDDictionaryEntryAVP& de, uint64_t v) {
    FDAvp avp(de);
    avp = v;
    return add(avp);
  }
  FDAvp& add(FDDictionaryEntryAVP& de, float v) {
    FDAvp avp(de);
    avp = v;
    return add(avp);
  }
  FDAvp& add(FDDictionaryEntryAVP& de, double v) {
    FDAvp avp(de);
    avp = v;
    return add(avp);
  }
  FDAvp& add(FDDictionaryEntryAVP& de, const char* v) {
    FDAvp avp(de);
    avp = v;
    return add(avp);
  }
  FDAvp& add(FDDictionaryEntryAVP& de, const std::string& v) {
    FDAvp avp(de);
    avp = v;
    return add(avp);
  }
  FDAvp& add(FDDictionaryEntryAVP& de, const char* v, size_t len) {
    FDAvp avp(de);
    avp.set(v, len);
    return add(avp);
  }
  FDAvp& add(FDDictionaryEntryAVP& de, const uint8_t* v, size_t len) {
    FDAvp avp(de);
    avp.set(v, len);
    return add(avp);
  }
  FDAvp& add(FDDictionaryEntryAVP& de, const STime& v) {
    FDAvp avp(de);
    avp = v;
    return add(avp);
  }

  FDAvp& operator=(int32_t v) { return set(v); }
  FDAvp& operator=(int64_t v) { return set(v); }
  FDAvp& operator=(uint32_t v) { return set(v); }
  FDAvp& operator=(uint64_t v) { return set(v); }
  FDAvp& operator=(float v) { return set(v); }
  FDAvp& operator=(double v) { return set(v); }
  FDAvp& operator=(const char* v) { return set(v); }
  FDAvp& operator=(const std::string& v) { return set(v); }
  FDAvp& operator=(const STime& v) { return set(v); }

  bool get(int32_t& v) {
    if (m_avphdr == NULL) return false;
    v = m_avphdr->avp_value->i32;
    return true;
  }
  bool get(uint32_t& v) {
    if (m_avphdr == NULL) return false;
    v = m_avphdr->avp_value->u32;
    return true;
  }
  bool get(uint64_t& v) {
    if (m_avphdr == NULL) return false;
    v = m_avphdr->avp_value->u64;
    return true;
  }
  bool get(float& v) {
    if (m_avphdr == NULL) return false;
    v = m_avphdr->avp_value->f32;
    return true;
  }
  bool get(double& v) {
    if (m_avphdr == NULL) return false;
    v = m_avphdr->avp_value->f64;
    return true;
  }
  bool get(int64_t& v);
  bool get(std::string& v);
  bool get(char* data, size_t& len);
  bool get(uint8_t* data, size_t& len);
  bool get(sSS& ss);
  bool get(STime& v);

  FDAvp& set(int32_t v) {
    m_value.i32 = v;
    assignValue();
    return *this;
  }
  FDAvp& set(uint32_t v) {
    m_value.u32 = v;
    assignValue();
    return *this;
  }
  FDAvp& set(uint64_t v) {
    m_value.u64 = v;
    assignValue();
    return *this;
  }
  FDAvp& set(float v) {
    m_value.f32 = v;
    assignValue();
    return *this;
  }
  FDAvp& set(double v) {
    m_value.f64 = v;
    assignValue();
    return *this;
  }
  FDAvp& set(const char* v) { return set(v, strlen(v)); }
  FDAvp& set(const uint8_t* v, size_t len) {
    m_value.os.data = (uint8_t*) v;
    m_value.os.len  = len;
    assignValue();
    return *this;
  }
  FDAvp& set(const std::string& v) { return set(v.c_str(), v.size()); }
  FDAvp& set(int64_t v);
  FDAvp& set(const char* v, size_t len);
  FDAvp& set(const STime& v);

  FDAvp getNext(bool& found);
  FDAvp getChild(bool& found);

  struct avp* getAvp() {
    return m_avp;
  }
  union avp_value* getAvpValue() {
    return &m_value;
  }
  FDAvp& setAvp(struct avp* a);

  FDAvp& add(FDExtractor& e);
  FDAvp& add(FDExtractorList& el);
  FDAvp& add(FDExtractorAvp& ea);
  FDAvp& add(FDExtractorAvpList& eal);

  FDAvp& addJson(const char* json);
  FDAvp& addJson(const std::string& json) { return addJson(json.c_str()); }
  bool getJson(std::string& json);

  void dump();

  FDDictionaryEntryAVP& getDictionaryEntry() { return *m_de; }

 protected:
  void addTo(msg_or_avp* reference);

 private:
  void init();
  void assignValue();

  FDDictionaryEntryAVP* m_de;
  struct avp* m_avp;
  struct avp_hdr* m_avphdr;
  union avp_value m_value;
  FDBuffer<uint8_t>* m_buf;
  bool m_assigned;
  bool m_dedel;
};

class FDMessageRequest;
class FDMessageAnswer;

class FDMessage {
 public:
  FDDictionaryEntryCommand* getCommand() { return m_de; }

  FDMessage& add(FDAvp& avp) {
    avp.addTo(m_msg);
    return *this;
  }
  FDMessage& add(FDDictionaryEntryAVP& de, int32_t v) {
    FDAvp avp(de);
    avp = v;
    return add(avp);
  }
  FDMessage& add(FDDictionaryEntryAVP& de, int64_t v) {
    FDAvp avp(de);
    avp = v;
    return add(avp);
  }
  FDMessage& add(FDDictionaryEntryAVP& de, uint32_t v) {
    FDAvp avp(de);
    avp = v;
    return add(avp);
  }
  FDMessage& add(FDDictionaryEntryAVP& de, uint64_t v) {
    FDAvp avp(de);
    avp = v;
    return add(avp);
  }
  FDMessage& add(FDDictionaryEntryAVP& de, float v) {
    FDAvp avp(de);
    avp = v;
    return add(avp);
  }
  FDMessage& add(FDDictionaryEntryAVP& de, double v) {
    FDAvp avp(de);
    avp = v;
    return add(avp);
  }
  FDMessage& add(FDDictionaryEntryAVP& de, const char* v) {
    FDAvp avp(de);
    avp = v;
    return add(avp);
  }
  FDMessage& add(FDDictionaryEntryAVP& de, const std::string& v) {
    FDAvp avp(de);
    avp = v;
    return add(avp);
  }
  FDMessage& add(FDDictionaryEntryAVP& de, const char* v, size_t len) {
    FDAvp avp(de);
    avp.set(v, len);
    return add(avp);
  }
  FDMessage& add(FDDictionaryEntryAVP& de, const uint8_t* v, size_t len) {
    FDAvp avp(de);
    avp.set(v, len);
    return add(avp);
  }
  FDMessage& add(FDDictionaryEntryAVP& de, const STime& v) {
    FDAvp avp(de);
    avp = v;
    return add(avp);
  }

  FDMessage& add(FDExtractor& e);
  FDMessage& add(FDExtractorList& el);
  FDMessage& add(FDExtractorAvp& ea);
  FDMessage& add(FDExtractorAvpList& eal);

  bool get(FDDictionaryEntryAVP& de, int32_t& v) {
    FDAvp avp = findAVP(de);
    return avp.get(v);
  }
  bool get(FDDictionaryEntryAVP& de, int64_t& v) {
    FDAvp avp = findAVP(de);
    return avp.get(v);
  }
  bool get(FDDictionaryEntryAVP& de, uint32_t& v) {
    FDAvp avp = findAVP(de);
    return avp.get(v);
  }
  bool get(FDDictionaryEntryAVP& de, uint64_t& v) {
    FDAvp avp = findAVP(de);
    return avp.get(v);
  }
  bool get(FDDictionaryEntryAVP& de, float& v) {
    FDAvp avp = findAVP(de);
    return avp.get(v);
  }
  bool get(FDDictionaryEntryAVP& de, double& v) {
    FDAvp avp = findAVP(de);
    return avp.get(v);
  }
  bool get(FDDictionaryEntryAVP& de, std::string& v) {
    FDAvp avp = findAVP(de);
    return avp.get(v);
  }
  bool get(FDDictionaryEntryAVP& de, char* v, size_t& len) {
    FDAvp avp = findAVP(de);
    return avp.get(v, len);
  }
  bool get(FDDictionaryEntryAVP& de, uint8_t* v, size_t& len) {
    FDAvp avp = findAVP(de);
    return avp.get(v, len);
  }
  bool get(FDDictionaryEntryAVP& de, STime& v) {
    FDAvp avp = findAVP(de);
    return avp.get(v);
  }

  FDAvp findAVP(FDDictionaryEntryAVP& de);
  FDAvp getFirstAVP(bool& found);

  void dump();

  bool isRequest() { return m_de->isRequest(); }
  bool isAnswer() { return m_de->isAnswer(); }

  struct msg* getMsg() {
    return m_msg;
  }

  void addOrigin();

  FDMessage& addJson(const char* json);
  FDMessage& addJson(const std::string& json) { return addJson(json.c_str()); }
  bool getJson(std::string& json);

 protected:
  FDMessage(
      bool req2ans, FDDictionaryEntryCommand* de, struct msg* pmsg = NULL,
      bool dedel = false, bool msgdel = true);
  FDMessage(
      FDDictionaryEntryCommand* de, struct msg* pmsg = NULL,
      bool dedel = false);
  FDMessage(
      FDDictionaryEntryApplication* ade, FDDictionaryEntryCommand* cde,
      struct msg* pmsg = NULL, bool dedel = false);
  ~FDMessage();

  FDMessage& sendRequest(
      void (*anscb)(void*, struct msg**), FDMessageRequest& req);
  FDMessage& sendAnswer();

  void setMsgDelete(bool v) { m_msgdel = v; }

 private:
  FDMessage();

  FDDictionaryEntryCommand* m_de;
  bool m_dedel;
  struct dict_cmd_data m_basedata;
  bool m_msgdel;
  struct msg* m_msg;
};

class FDMessageAnswer : public FDMessage {
  friend FDMessageRequest;

 public:
  FDMessageAnswer(
      FDDictionaryEntryCommand* cmd,
      struct msg* pmsg);  // used to encapsulate a FD answer message
  FDMessageAnswer(FDMessageRequest* req);  // used to create and encapsulate a
                                           // FD answer from a request

  FDMessageAnswer& send();
};

class FDMessageRequest : public FDMessage {
  friend FDMessageAnswer;

 public:
  FDMessageRequest(FDDictionaryEntryCommand* cde);
  FDMessageRequest(FDDictionaryEntryCommand* cde, struct msg* pmsg);
  FDMessageRequest(
      FDDictionaryEntryApplication* ade, FDDictionaryEntryCommand* cde);
  virtual ~FDMessageRequest();

  FDMessageRequest& send();

  virtual void processAnswer(FDMessageAnswer& ans);

 protected:
  STimerElapsed m_timer;

 private:
  static void anscb(void* data, struct msg** pmsg);
};

class FDCommand {
 public:
  FDDictionaryEntryCommand& getDictionaryEntry() { return m_de; }

  virtual bool isRequest() = 0;
  bool isAnswer() { return !isRequest(); }

 protected:
  FDCommand(FDDictionaryEntryCommand& de);

 private:
  FDCommand();

  FDDictionaryEntryCommand& m_de;
};

class FDCommandRequest : public FDCommand {
 public:
  FDCommandRequest(FDDictionaryEntryCommand& de);

  bool isRequest() { return true; }

  virtual int process(FDMessageRequest* req) = 0;
};

#define FDISREQUEST(hdr)                                                       \
  ((hdr->msg_flags & CMD_FLAG_REQUEST) == CMD_FLAG_REQUEST)
#define FDISANSWER(hdr)                                                        \
  ((hdr->msg_flags & CMD_FLAG_REQUEST) != CMD_FLAG_REQUEST)

class FDApplication {
 public:
  FDApplication(FDDictionaryEntryApplication* de);
  ~FDApplication();

  FDDictionaryEntryApplication& getDictionaryEntry() { return *m_de; }

  FDApplication& registerHandler(FDCommandRequest& cmd);

 protected:
  FDApplication() : m_de(NULL) {}
  FDDictionaryEntryApplication* setDictionaryEntry(
      FDDictionaryEntryApplication* de) {
    return m_de = de;
  }

 private:
  static int commandcb(
      struct msg** m, struct avp* avp, struct session* session, void* data,
      enum disp_action* action);

  FDDictionaryEntryApplication* m_de;
  std::list<FDCommandRequest*> m_cmds;
};

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

class FDSession {
 public:
  FDSession();
  ~FDSession();

  const std::string& getSessionId();
  void addSessionId(FDMessage& msg, FDDictionaryEntryAVP& deSessionId);

 private:
  void init();

  struct session* m_session;
  std::string m_sid;
};

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

enum FDPeerState {
  PSInvalid = -1,
  PSNew     = 0,
  PSOpen,
  PSClosed,
  PSClosing,
  PSWaitCnxAck,
  PSWaitCnxAckElec,
  PSWaitCEA,
  PSOpenHandshake,
  PSSuspect,
  PSReOpen,
  PSOpenNew,
  PSClosingGrace,
  PSZombie
};

class FDPeer {
 public:
  FDPeer();
  FDPeer(DiamId_t diamid, uint16_t port = 3868);
  FDPeer(const std::string& diamid, uint16_t port = 3868);

  const std::string& getDiameterId() { return m_diamid; }
  FDPeer& setDiameterId(DiamId_t diamid) {
    m_diamid = diamid;
    return *this;
  }

  uint16_t getPort() { return m_port; }
  FDPeer& setPort(uint16_t v) {
    m_port = v;
    return *this;
  }

  const std::string& getDestinationHost() const { return m_diamid; }
  const std::string& getDestinationRealm() const { return m_destrealm; }

  const std::string& setDestinationIp(const char* ip) {
    m_destip = ip;
    return getDestinationIp();
  }
  const std::string& setDestinationIp(const std::string& ip) {
    m_destip = ip;
    return getDestinationIp();
  }
  const std::string& getDestinationIp() const { return m_destip; }

  FDPeerState getState();

  bool isOpen() { return getState() == PSOpen; }

  void add();

 private:
  void init();
  static void peercb(struct peer_info* pi, void* data);

  std::string m_diamid;
  std::string m_destrealm;
  std::string m_destip;
  uint16_t m_port;
  struct peer_hdr* m_peer;
};

class FDPeerList : public std::list<FDPeer*> {
 public:
  FDPeerList();
  ~FDPeerList();

  bool isPeerOpen();

  FDPeer* getOpenPeer();
};

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

class FDExtractorAvp;

enum eFDExtractorType { etAvp, etAvpList, etExtractor, etExtractorList };

class FDExtractorBase {
 public:
  FDExtractorBase(FDDictionaryEntryAVP* de)
      : m_de(de), m_idx(-1), m_resolved(false) {}

  virtual ~FDExtractorBase() {}

  virtual eFDExtractorType getExtractorType() = 0;

  int getIndex() { return m_idx; }
  int setIndex(int idx) { return m_idx = idx; }

  bool getResolved() { return m_resolved; }
  bool setResolved(bool resolved = true) { return m_resolved = resolved; }

  bool exists() { return m_idx != -1; }

  FDDictionaryEntryAVP* getDictionaryEntry() { return m_de; }

 private:
  FDDictionaryEntryAVP* m_de;
  int m_idx;
  bool m_resolved;
};

class FDExtractorKey {
 public:
  FDExtractorKey() : m_vndid(0), m_avpcode(0) {}

  FDExtractorKey(vendor_id_t v, avp_code_t a) : m_vndid(v), m_avpcode(a) {}

  FDExtractorKey(const FDExtractorKey& k)
      : m_vndid(k.m_vndid), m_avpcode(k.m_avpcode) {}

  virtual ~FDExtractorKey() {}

  bool operator<(const FDExtractorKey& rval) const {
    return m_vndid < rval.m_vndid ?
               true :
               m_vndid > rval.m_vndid ? false : m_avpcode < rval.m_avpcode;
  }

  vendor_id_t getVendor() { return m_vndid; }
  vendor_id_t setVendor(vendor_id_t v) { return m_vndid = v; }

  avp_code_t getAvpCode() { return m_avpcode; }
  avp_code_t setAvpCode(avp_code_t a) { return m_avpcode = a; }

 private:
  vendor_id_t m_vndid;
  avp_code_t m_avpcode;
};

class FDExtractor : public FDExtractorBase {
  friend FDExtractorList;
  friend FDExtractorAvp;
  friend FDExtractorAvpList;

 public:
  FDExtractor();
  FDExtractor(FDMessage& msg);
  FDExtractor(FDDictionaryEntryCommand& de);
  FDExtractor(FDExtractor& parent, FDDictionaryEntryAVP& de);
  virtual ~FDExtractor();

  eFDExtractorType getExtractorType() { return etExtractor; }

  void setReference(FDMessage& msg) { m_reference = msg.getMsg(); }
  void setReference(FDAvp& avp) { m_reference = avp.getAvp(); }
  void setReference(msg_or_avp* m) { m_reference = m; }

  FDExtractor* getParent() { return m_parent; }
  msg_or_avp* getReference();

  void add(FDExtractorBase& base);

  bool exists(bool skipResolve = false);

  void dump();

  bool getJson(std::string& json);

 protected:
  void resolve();

 private:
  FDExtractor* m_parent;
  msg_or_avp* m_reference;
  std::map<FDExtractorKey, FDExtractorBase*> m_entries;
  int m_index;
};

class FDExtractorList : public FDExtractorBase {
  friend FDExtractor;
  friend FDAvp;
  friend FDMessage;

 public:
  FDExtractorList(FDExtractor& parent, FDDictionaryEntryAVP& de);
  virtual ~FDExtractorList();

  eFDExtractorType getExtractorType() { return etExtractorList; }

  FDExtractor& getParent() { return *m_parent; }

  virtual FDExtractor* createExtractor() = 0;

  void addExtractor(FDExtractor* e);

  bool exists();

  void dump();

 protected:
  std::list<FDExtractor*>& getList();

 private:
  FDExtractorList();

  FDExtractor* m_parent;
  std::list<FDExtractor*> m_list;
};

class FDExtractorAvp : public FDExtractorBase {
 public:
  FDExtractorAvp(
      FDExtractor& extractor, FDDictionaryEntryAVP& de, bool dedel = false);
  virtual ~FDExtractorAvp();

  eFDExtractorType getExtractorType() { return etAvp; }

  void setAvp(struct avp* a) { m_avp.setAvp(a); }
  struct avp* getAvp() {
    return m_avp.getAvp();
  }
  union avp_value* getAvpValue() {
    return m_avp.getAvpValue();
  }

  bool exists();

  bool get(int32_t& v) {
    if (!exists()) return false;
    return m_avp.get(v);
  }
  bool get(uint32_t& v) {
    if (!exists()) return false;
    return m_avp.get(v);
  }
  bool get(uint64_t& v) {
    if (!exists()) return false;
    return m_avp.get(v);
  }
  bool get(float& v) {
    if (!exists()) return false;
    return m_avp.get(v);
  }
  bool get(double& v) {
    if (!exists()) return false;
    return m_avp.get(v);
  }
  bool get(int64_t& v) {
    if (!exists()) return false;
    return m_avp.get(v);
  }
  bool get(std::string& v) {
    if (!exists()) return false;
    return m_avp.get(v);
  }
  bool get(char* data, size_t& len) {
    if (!exists()) return false;
    return m_avp.get(data, len);
  }
  bool get(uint8_t* data, size_t& len) {
    if (!exists()) return false;
    return m_avp.get(data, len);
  }
  bool get(sSS& ss) {
    if (!exists()) return false;
    return m_avp.get(ss);
  }
  bool get(STime& t) {
    if (!exists()) return false;
    return m_avp.get(t);
  }

  void dump();

  bool getJson(std::string& json);

 private:
  FDExtractor& m_extractor;
  FDAvp m_avp;
};

class FDExtractorAvpList : public FDExtractorBase {
 public:
  FDExtractorAvpList(FDExtractor& extractor, FDDictionaryEntryAVP& de);
  virtual ~FDExtractorAvpList();

  eFDExtractorType getExtractorType() { return etAvpList; }

  std::list<FDExtractorAvp*>& getList();

  bool exists();

  void dump();

 private:
  FDExtractorAvpList();

  FDExtractor* m_parent;
  std::list<FDExtractorAvp*> m_list;
};

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

class FDUtility {
 public:
  static void splitDiameterFQDN(
      std::string& fqdn, std::string& host, std::string& realm);
  static void splitDiameterFQDN(
      const char* fqdn, std::string& host, std::string& realm) {
    std::string sfqdn(fqdn);
    splitDiameterFQDN(sfqdn, host, realm);
  }

  static size_t str2tbcd(
      const char* src, size_t srclen, uint8_t* dst, size_t dstlen);
  static size_t str2tbcd(const char* src, uint8_t* dst, size_t dstlen);
  static size_t str2tbcd(const std::string& src, uint8_t* dst, size_t dstlen);

  static size_t tbcd2str(uint8_t* src, size_t srclen, char* dst, size_t dstlen);
  static size_t tbcd2str(uint8_t* src, size_t srclen, std::string& dst);
};

#endif  // #define __FD_H
